module jinji.util.query;

import jinji.util.error;
import std.ascii;
public import tame.conv : intToStr;

/// 筛选操作（奇数和偶数的操作相反）
enum FilterOp {
	/// 等于
	eq,
	/// 不等于
	ne,
	/// 大于
	gt,
	/// 小于等于
	le,
	/// 大于等于
	ge,
	/// 小于
	lt,
	/// 包含
	like,
	/// 不包含
	notLike,
	/// 为空
	isNull,
	/// 不为空
	isNotNull,
	/// 匹配正则
	regexp = 10,
	/// 不匹配正则
	notRegexp,
	/// 自定义
	custom = -1
}

/// 逻辑操作
enum BoolOp {
	/// 与
	and,
	/// 或
	or,
	/// 异或
	xor
}

/// 筛选条件
struct Filter {
	/// 字段名
	string key;
	/// 操作
	FilterOp op;
	/// 值
	string value;
	/// 逻辑操作
	BoolOp conj;
}

alias isStringSink(T) =
	imported!"std.range".isOutputRange!(T, const(char)[]);

/// 函数白名单
immutable whiteList = [
	// math
	"abs", "acos", "asin", "atan", "atan2", "atanh", "ceil", "cos", "cosh",
	"degrees", "exp", "floor", "ln", "log", "log10", "log2", "max", "min",
	"mod", "pi", "pow", "radians", "round", "sign",
	"sin", "sinh", "sqrt", "tan", "tanh", "trunc",
	// control
	"coalesce", "ifnull", "iif", "nullif",
	// conv
	"cast", "lower", "upper", "hex", "unhex",
	// string
	"length",
	"random", "randomblob", "zeroblob", "instr", "quote", "char", "unicode",
	"like", "glob", "match", "regexp", "replace", "substr", "trim", "ltrim",
	"rtrim",
	// custom
	"now",
].toAA();

private auto toAA(T)(in T[] arr) {
	bool[T] aa;
	foreach (e; arr)
		aa[e] = true;
	return aa;
}

/++
解析排序规则表达式
Params:
	s = 排序规则表达式
	r = 缓冲区
Return:
	排序规则
Throws: ArgEx
+/
auto parseCE(R)(in char[] s, ref scope R r) @trusted {
	import std.ascii : isAtoz = isAlpha;
	import tame.string,
	tame.text.ascii;

	size_t p;
	bool quoted;

	if (!s.length || s.canFind(';') || s[$ - 1] == ',')
		goto err;
	for (size_t i; i <= s.length; ++i) {
		const c = i == s.length ? ' ' : s[i];
		if (quoted) {
			if (!isAlphaNum_(c)) {
				if (c == '(' || (i > 4 && i - p == 4 && s[i - 4 .. i] == "desc"
						&& !isAlphaNum_(s[i - 5]))) {
					if (c == '(') {
						const func = s[p .. i];
						checkArg(s.stringof, (func in whiteList) !is null,
							`未知函数"` ~ cast(string)func ~ '"');
					}
					r ~= s[p .. i];
				} else {
					r ~= '"';
					r ~= s[p .. i];
					r ~= '"';
				}
				p = i;
				quoted = false;
			}
		} else if (isAtoz(c) || c == '_') {
			r ~= s[p .. i];
			p = i;
			quoted = true;
		}
	}
	r ~= s[p .. $];
	if (r.data.length)
		return r.data;
err:
	throw new ArgEx(`无效的排序表达式"` ~ cast(string)s ~ '"');
}

version (unittest) {
	import std.array : appender;
	import std.exception;

	private void test(in char[] order, string expected) @safe {
		auto r = appender!string;
		auto result = parseCE(order, r);
		assert(result == expected, result);
	}
}

@safe unittest {
	auto buf = appender!string;
	assertThrown!ArgEx(parseCE("", buf));
	assertThrown!ArgEx(parseCE("a,", buf));
	test("a", `"a"`);
	test("a desc", `"a" desc`);
	test("a,b", `"a","b"`);
	test("a desc,b", `"a" desc,"b"`);
	test("length(a),b desc", `length("a"),"b" desc`);
	test("length(a)desc,b", `length("a")desc,"b"`);
	test("a,random()", `"a",random()`);
	test("min(30,length(a)+1),b desc", `min(30,length("a")+1),"b" desc`);
}

/// 转义字段名
void escapeKey(S)(in char[] key, ref scope S sink) @trusted
if (isStringSink!S) {
	size_t start;
	foreach (i, c; key) {
		string escaped = void;

		// dfmt off
		switch (c) {
		case '\'': escaped = `\'`; break;
		case '"': escaped = `\"`; break;
		default:
			checkArg(key.stringof, c > ' ' && c != '\\', "字段名包含非法字符");
			continue;
		}
		// dfmt on

		sink ~= key[start .. i];
		sink ~= escaped;
		start = i + 1;
	}

	sink ~= key[start .. $];
}

/// 转义SQL通配符
void escapeValue(S)(in char[] expr, ref S sink) @trusted {
	size_t start;

	foreach (i, c; expr) {
		string escaped = void;

		// dfmt off
		switch (c) {
		case '%': escaped = `\%`; break;
		case '_': escaped = `\_`; break;
		case '\\': escaped = `\\`; break;
		default: continue;
		}
		// dfmt on

		sink ~= expr[start .. i];
		sink ~= escaped;
		start = i + 1;
	}

	sink ~= expr[start .. $];
}

void write(S)(ref scope S s, size_t x) @trusted {
	s.pos += intToStr(&s[s.pos], x);
}

/// 解析查询条件
void parseFilters(S)(in Filter[] filters, ref scope S s) {
	foreach (i, filter; filters) {
		escapeKey(filter.key, s);
		switch (filter.op) with (FilterOp) {
		case eq:
			s ~= `=$`;
			s.write(i + 1);
			break;
		case ne:
			s ~= `!=$`;
			s.write(i + 1);
			break;
		case gt:
			s ~= `>$`;
			s.write(i + 1);
			break;
		case ge:
			s ~= `>=$`;
			s.write(i + 1);
			break;
		case lt:
			s ~= `<$`;
			s.write(i + 1);
			break;
		case le:
			s ~= `<=$`;
			s.write(i + 1);
			break;
		case like:
			s ~= ` LIKE $`;
			s.write(i + 1);
			s ~= ` ESCAPE '\'`;
			break;
		case notLike:
			s ~= ` NOT LIKE $`;
			s.write(i + 1);
			s ~= ` ESCAPE '\'`;
			break;
		case isNull:
			s ~= ` IS NULL`;
			break;
		case isNotNull:
			s ~= ` IS NOT NULL`;
			break;
			version (withRE) {
		case regexp:
				s ~= ` REGEXP $`;
				s.write(i + 1);
				break;
		case notRegexp:
				s ~= ` NOT REGEXP $`;
				s.write(i + 1);
				break;
			}
		case custom:
			s ~= filter.value;
			break;
		default:
			throw new ArgEx(_("不支持的操作符"));
		}
		if (i + 1 < filters.length) {
			switch (filter.conj) with (BoolOp) {
			case and:
				s ~= " AND ";
				break;
			case or:
				s ~= " OR ";
				break;
			default:
				throw new ArgEx(_("不支持的逻辑运算符"));
			}
		}
	}
}

/// 生成固定长度的缓冲区
auto fixedBuf(size_t len, string err)() @trusted {
	import jinji.util.error,
	tame.buffer;

	FixedBuffer!len buf = void;
	buf.pos = 0;
	buf.outputFunc = (in char[]) => throw new ArgOutOfRangeEx(err);
	return buf;
}

unittest {
	auto buf = fixedBuf!(1, "err");
	try {
		buf ~= "ab";
		assert(0);
	} catch (Exception e) {
		assert(e.msg == "err");
	}
}
